package top.amot.forceview.force

import top.amot.forceview.layout.I_Layout
import top.amot.forceview.model.AbstractLink
import top.amot.forceview.model.AbstractNode
import kotlin.math.sqrt

/**
 * 节点连线的处理先初始化连线，
 * 计算节点的度和每一条边对起始节点度 (source/target degree) 的占比，
 * 使 ，alpha 为阻尼系数，默认边长度 (distance) 为 30，默认弹簧劲度 (strength) 为 ，
 * 减少度大节点的引力，提高稳定性。计算连线两边的引力，
 * 最终推导出速度的变化。
 */

class ForceLink : AbstractForce() {
    private lateinit var links: List<AbstractLink<AbstractNode,AbstractNode>>
    private lateinit var distances: FloatArray
    private lateinit var strengths: FloatArray
    private lateinit var bias: FloatArray
    private lateinit var count: FloatArray
    private var strengthCalculation: LinkCalculation? = null
    private var distanceCalculation: LinkCalculation? = null

    override fun initialize(layout: I_Layout) {
        super.initialize(layout)
        this.links=layout.currentLinkList()
        initialize()
    }

    override fun apply(alpha: Float) {
        for (k in 0 until ITERATIONS) {
            links.forEachIndexed { i, link ->
                val source = link.source
                val target = link.target
                var x = target.x + target.vx - source.x - source.vx
                var y = target.y + target.vy - source.y - source.vy
                if (x == 0.0f) {
                    x = jiggle()
                }
                if (y == 0.0f) {
                    y = jiggle()
                }
                var l = sqrt(x * x + y * y)
                l = (l - distances[i]) / l * alpha * strengths[i]
                x *= l
                y *= l
                var b = bias[i]
                target.vx -= x * b
                target.vy -= y * b
                b = 1 - b
                source.vx += x * b
                source.vy += y * b
            }
        }
    }

    fun strength(c: LinkCalculation?): ForceLink {
        strengthCalculation = c
        return this
    }

    fun distance(c: LinkCalculation?): ForceLink {
        distanceCalculation = c
        return this
    }

    fun links(links: List<AbstractLink<AbstractNode,AbstractNode>>): ForceLink {
        this.links=links
        return this
    }

    private fun initialize() {
        count = FloatArray(nodes.size)
        for (i in links.indices) {
            val link = links[i]
            link.index = i
            count[link.source.index] += 1.0f
            count[link.target.index] += 1.0f
        }
        bias = FloatArray(links.size)
        for (i in links.indices) {
            val link = links[i]
            bias[i] =
                count[link.source.index] / (count[link.source.index] + count[link.target.index])
        }
        strengths = FloatArray(links.size)
        initializeStrength()
        distances = FloatArray(links.size)
        initializeDistance()
    }

    private fun initializeStrength() {
        for (i in links.indices) {
            strengths[i] = strengthCalc(i)
        }
    }

    private fun initializeDistance() {
        for (i in links.indices) {
            distances[i] = distanceCalc(i)
        }
    }

    private fun strengthCalc(i: Int): Float {
        val link = links[i]
        return strengthCalculation?.calculateLink(link)?: (1 / count[link.source.index].coerceAtMost(count[link.target.index]))
    }

    private fun distanceCalc(i: Int): Float {
        val link = links[i]
        val distance =  distanceCalculation?.calculateLink(link)?:-1.0f

        return if (distance < 0) {
            DEFAULT_DISTANCE
        } else {
            distance
        }
    }

    companion object {
        const val NAME = "Link"
        private const val ITERATIONS = 1
        private const val DEFAULT_DISTANCE = 30.0f
    }
}